DictVectorizerで辞書のリストを処理する機械学習パイプラインを作る
データアナリティクス事業本部の鈴木です。
今回は、sklearn.feature_extractionモジュールから、辞書のリストをNumPy配列やSciPyのスパース行列に変換するDictVectorizerを紹介します。
DictVectorizerとは
特徴量名と特徴量の値の辞書のリストを、NumPy配列やSciPyのスパース行列に変換し、scikit-learnのestimatorで使用できるようにするtransformerです。
sklearn.feature_extraction.DictVectorizer — scikit-learn 0.24.2 documentation
準備
検証した環境
- コンテナ:jupyter/datascience-notebook
- scikit-learn:0.24.2
データの作成
今回は、sklearn.datasetsモジュールのアイリスデータセットを使います。アイリスデータセットは言わずと知れた有名なデータセットで、3種のアヤメの情報が合計150サンプル分入っています。
データを作成していきます。少しまわりくどいかもしれませんが、いったんpandasのDataFrameに変換し、JSON形式のカラムを作成した後、リストに変換します。
import pandas as pd from sklearn import datasets # アヤメのデータをsklearnから読み出す。 iris = datasets.load_iris() # pandasデータフレームに変換する。データをJSONに変換しやすいため。 # カラム名にスペースが入っていたので、扱いやすい名前に変えた。 feature_names = ["sepal_length", "sepal_width", "petal_length", "petal_width"] df_iris = pd.DataFrame(iris["data"], columns=feature_names) # 各行が何の種類なのか取り出す。 df_iris['species'] = iris.target_names[iris["target"]] # 行ごとにJSON文字列に変換し、新しい列に格納する。 df_iris["iris_json"] = df_iris.apply(lambda row: json.loads(row.to_json()), axis=1) # 作成したカラムをリストに変換する。 iris_json_list = df_iris["iris_json"].values.tolist() ## [{'sepal_length': 5.1, ## 'sepal_width': 3.5, ## 'petal_length': 1.4, ## 'petal_width': 0.2, ## 'species': 'setosa'},...
やってみる
DictVectorizerの動作を確認する
DictVectorizerインスタンスを作成し、fit_transformメソッドで作成したデータを変換してみましょう。
また、get_feature_namesメソッドで変換後の特徴量名も確認してみます。
from sklearn.feature_extraction import DictVectorizer vec = DictVectorizer() # 変換し、最初のレコードの結果を確認する。 result = vec.fit_transform(iris_json_list).toarray() result[0] ## array([1.4, 0.2, 5.1, 3.5, 1. , 0. , 0. ]) # 各特徴の名前を確認する vec.get_feature_names() ## ['petal_length', ## 'petal_width', ## 'sepal_length', ## 'sepal_width', ## 'species=setosa', ## 'species=versicolor', ## 'species=virginica']
fit_transformで変換すると、カテゴリ変数はOne-hot表現で出力されます。あるJSONに該当するキー・バリューがなかった場合は、検証したバージョンでは、全て0のOne-hot表現になりました。
変換後の特徴量名は、セパレータがデフォルトだと"キー名=カテゴリ名"
のようになります。セパレータはseparator引数で指定が可能です。
inverse_transformで、結果を辞書のリストへと変換することも可能です。
# arrayやsparse matrixを辞書に変換する vec.inverse_transform(result) ## [{'petal_length': 1.4, ## 'petal_width': 0.2, ## 'sepal_length': 5.1, ## 'sepal_width': 3.5, ## 'species=setosa': 1.0},...
DictVectorizerを使ってパイプラインを構築する
辞書のリストを入力として与えられている想定で、以下のようなパイプラインを構築してみます。
- 辞書のリストを読み込む。
- 量的変数は標準化する。
- カテゴリ変数はOne-hot表現のまま触らない。
今回は以下のように実装しました。
from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.preprocessing import FunctionTransformer # ColumnTransformerの作成 preprocessor = ColumnTransformer( transformers=[ ('numeric_transformer', StandardScaler(), [0, 1, 2, 3]), ('categorical_transformer', FunctionTransformer(), [4, 5, 6])]) # 全体のパイプラインの作成 pipe = Pipeline([ ("json_loader", DictVectorizer(sparse=False)), ("preprocessor", preprocessor)])
パイプラインをダイアグラムで表示すると以下のようになります。
from sklearn import set_config set_config(display='diagram') pipe
パイプラインを作成する際には、以下のことに気を付けました。
- 今回はDictVectorizerのsparse引数をFalseに設定しました。SciPyのスパース行列をStandardScalerに渡す際、StandardScalerはwith_mean引数をFalseにする必要があり、標準化の際に平均が引かれなくなってしまうためです。今回のデータでは、One-hot表現のカラムが3つ増えるだけなので、大きな影響はないだろうと思い、このように設定しています。
-
DictVectorizerは結果をNumPy配列かSciPyのスパース行列で返すため、ColumnTransformerを使ってTransformerをカラムごとに適用できるよう、カラムをインデックスで指定しました。カラムのインデックスは、事前に、データにDictVectorizerを試しておき、get_feature_namesメソッドを使って調べるのが良さそうでした。
-
ColumnTransformerを使う際、カテゴリ変数にはFunctionTransformerをfunc引数なしで適用しました。func引数なしの場合、FunctionTransformerは恒等関数として利用できます。
変換結果は以下のようになります。
# 変換結果を出力する。 pipe.fit_transform(iris_json_list) ## array([[-1.32790667, -1.30363303, -0.89529213, ..., 1. , ## 0. , 0. ], ## ...
最後に
sklearn.feature_extractionモジュールのDictVectorizerの使い方と、DictVectorizerを使った機械学習パイプラインの例を紹介しました。
DictVectorizerはカテゴリ変数をOne-hot表現に変換するため、カテゴリ変数のカテゴリ数が多い場合は、事前にそのキー・バリューは落としておく必要がありそうでした。
辞書のリストを直接インプットにできるのは、JSONファイルをインプットとするような際にとても便利なので、ぜひ使っていきたいです。